package ru.ifmo.ctd.ngp.theory.xdivk.onemax;

import java.util.ArrayList;
import java.util.List;

import ru.ifmo.ctd.ngp.demo.ffchooser.genetic.print.CompactPrinter;
import ru.ifmo.ctd.ngp.demo.ffchooser.genetic.print.CountPrinter;
import ru.ifmo.ctd.ngp.demo.ffchooser.reinforce.OAEnvironment;
import ru.ifmo.ctd.ngp.demo.ffchooser.reinforce.ReinforcementCounter;
import ru.ifmo.ctd.ngp.demo.ffchooser.reinforce.reward.ComplexFixedReward;
import ru.ifmo.ctd.ngp.demo.ffchooser.reinforce.reward.RewardCalculator;
import ru.ifmo.ctd.ngp.demo.ffchooser.switchpoint.FunctionalFitness;
import ru.ifmo.ctd.ngp.demo.ffchooser.switchpoint.FunctionalGeneticAlgorithm;
import ru.ifmo.ctd.ngp.demo.ffchooser.switchpoint.IntegerFunctionImpl;
import ru.ifmo.ctd.ngp.demo.ffchooser.switchpoint.RealFunction;
import ru.ifmo.ctd.ngp.demo.ffchooser.switchpoint.f.X;
import ru.ifmo.ctd.ngp.demo.ffchooser.utils.GeneticUtils;
import ru.ifmo.ctd.ngp.demo.ffchooser.xvector.BitCountFitness;
import ru.ifmo.ctd.ngp.demo.ffchooser.xvector.IntFitness;
import ru.ifmo.ctd.ngp.ds.string.GStringBuilderX;
import ru.ifmo.ctd.ngp.ds.string.GStringX;
import ru.ifmo.ctd.ngp.ds.string.impl.ObjString;
import ru.ifmo.ctd.ngp.learning.reinforce.DynaAgent;

public class OneMaxDynaRunner {

	public static void main(String[] args) {
		runDyna(ObjString.<Boolean>empty());
	}

	private static <S extends GStringX<Boolean, S, B>,
					B extends GStringBuilderX<Boolean, B, S>> void runDyna(S sample) {
		/**
		 * Task and Evolution parameters
		 */
		int stepsLimit = 50000;
		int length = 200;
		
		int generationSize = 1;
		
		/**
		 * Fitness and EA configuration
		 */		
		RealFunction OneMax = new X();
		RealFunction ZeroMax = new Y(length);
		
		List<FunctionalFitness> evaluators = new ArrayList<>();
		evaluators.add(new FunctionalFitness(new IntegerFunctionImpl(OneMax, 1, 0, length)));	//onemax
		evaluators.add(new FunctionalFitness(new IntegerFunctionImpl(ZeroMax, 1, 0, length)));	//zeromax
		
		FunctionalGeneticAlgorithm<S, B> alg = FunctionalGeneticAlgorithm.newFGA(0/*length*/, 0, 0, evaluators, 0, 0, sample);
		alg.setGenerationSize(generationSize);
		alg.setStartPopulation(GeneticUtils.zeroPopulation(length, generationSize, sample));
		alg.setEliteCount(0);
		
		RewardCalculator reward = new ComplexFixedReward(-1, 0, 1, (double)1 / evaluators.size());
		
		/**
		 * Agent and environment configuration
		 */
		DynaAgent<String, Integer> agent = new DynaAgent<String, Integer>(0.01/*probability*/, 0.2/*discount*/, 150/*k*/);

		OAEnvironment env = new OAEnvironment(alg, reward);
		
		ReinforcementCounter<S> counter = new ReinforcementCounter<>(stepsLimit, agent, alg, env);
		
		counter.setLength(length);
		counter.setEvaluator(new IntFitness(new BitCountFitness(1.0, 0)), OneMax.getValue(length));
		
		CountPrinter<S> countPrinter = new CountPrinter<>(new BitCountFitness(1.0, 0), evaluators.size());
		alg.addPrinter(countPrinter);
		
		DynaConsolePrinter.addTo(agent);
		alg.addPrinter(new CompactPrinter<S>());
		
		double times = 1;
		int zeroDelta = 25;
		
		for (int i = 0; i < times; i++) {
			System.out.println("Run #" + i);			
			countPrinter.refresh();
			counter.countGenerations();	
			printStatistics(countPrinter, length, evaluators.size(), length / 2, length / 2);
			double[][] result = countPercents(countPrinter, length / 2, length, zeroDelta);
			
			for (int k = 0; k < 2; k++) {
				for (int j = 0; j < evaluators.size(); j++) {
					System.out.print(result[k][j] + "% ");
				}
				System.out.println();
			}
		}
	}
//===========================================================================================================================================================	
	private static void printStatistics(
			CountPrinter<?> countPrinter, 
			int length, 
			int evaluatorsCount, 
			int transitStart,
			int transitFinish) {
		
		String[] names = {"before", "transit", "after"};
		int segments = names.length;
		
		List<int[]> sums = new ArrayList<>();
        for (String ignored : names) {
            sums.add(new int[evaluatorsCount]);
        }
		
		System.out.println();
		for (int i = 0; i <= length; i++) {
			boolean empty = true;
			
			for (int times : countPrinter.getStatistics(i)) {
				if (times != 0) {
					empty = false;
				}
			}
			
			if (empty) {
				continue;
			}
			
			System.out.print(i + " ");
			
			int[] statistics = countPrinter.getStatistics(i);
			
			for (int j = 0; j < statistics.length; j++) {
				if (i < transitStart) {
					sums.get(0)[j] += statistics[j];
				}				
				if (i >= transitStart && i < transitFinish) {
					sums.get(1)[j] += statistics[j];
				}				
				if (i >= transitFinish) {
					sums.get(2)[j] += statistics[j];
				}	
				System.out.print(statistics[j] + " ");
			}
			System.out.println();
		}
		
		int[] total = new int[segments];
		
		System.out.println();
		
		for (int i = 0; i < evaluatorsCount; i++) {
			System.out.print("evaluator #" + i + " ");
			for (int j = 0; j < segments; j++) {
				System.out.print(names[j] + " " + sums.get(j)[i] + " ");
				total[j] += sums.get(j)[i];
			}
			System.out.println();
		}
		
		System.out.println();
		
		for (int i = 0; i < segments; i++) {
			System.out.print(names[i] + " ");
			for (int j = 0; j < evaluatorsCount; j++) {
				System.out.print("#" + j + " " + ((double) sums.get(i)[j] / total[i]) * 100 + "% ");
			}
			System.out.println();
		}
	}
//--------------------------------------------------------------------------------------------------------------------------------------------------------	
	private static double[][] countPercents(
			CountPrinter<?> countPrinter,
			int switchPoint, 
			int length, 
			int delta) {
		int size = countPrinter.getEvaluatorsCount();
		
		double[] before = new double[size];
		double total = 0;
		
		for (int i = switchPoint - delta; i < switchPoint; i++) {
			for (int j = 0; j < size; j++) {
				int stat = countPrinter.getStatistics(i)[j];
				before[j] += stat;
				total += stat;				
			}
		}
		
		for (int i = 0; i < size; i++) {
			before[i] /= total;
			before[i] *= 100;
		}
		
		double[] after = new double[size];
		total = 0;
		
		for (int i = switchPoint; i <= length; i++) {
			for (int j = 0; j < size; j++) {
				int stat = countPrinter.getStatistics(i)[j];
				after[j] += stat;
				total += stat;				
			}
		}
		
		for (int i = 0; i < size; i++) {
			after[i] /= total;
			after[i] *= 100;
		}
		
		return new double[][] {before, after};
	}
}
